package me.zhengjie.modbusTCP;

import com.digitalpetri.modbus.master.ModbusTcpMaster;
import com.digitalpetri.modbus.master.ModbusTcpMasterConfig;
import com.digitalpetri.modbus.requests.ReadHoldingRegistersRequest;
import com.digitalpetri.modbus.requests.WriteSingleRegisterRequest;
import com.digitalpetri.modbus.responses.ModbusResponse;
import com.digitalpetri.modbus.responses.ReadHoldingRegistersResponse;
import com.digitalpetri.modbus.responses.WriteSingleRegisterResponse;
import io.netty.buffer.ByteBuf;
import io.netty.util.ReferenceCountUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhengjie.config.NetConfig;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
@Component
@RequiredArgsConstructor
public class NoThreeUnbindingTCP {

    private final NetConfig netConfig;
    private final ConcurrentHashMap<Integer, Object> lockMap = new ConcurrentHashMap<>();

    static ModbusTcpMaster master;
    /**
     * 获取TCP协议的Master
     */
    @PostConstruct
    public void initModbusTcpMaster() {
        try {
            if (master == null) {
                // 创建配置
                ModbusTcpMasterConfig config = new ModbusTcpMasterConfig.Builder(netConfig.getNoThreeUnbindingPlcIp()).setPort(netConfig.getNoThreeUnbindingPlcPort()).build();
                master = new ModbusTcpMaster(config);
                master.connect();
            }
        } catch (Exception e) {
            log.error("三号拆解区plc连接失败，失败原因：{}",e.toString());
        }
    }

    private final ConcurrentHashMap<Integer, ReentrantLock> addressLocks = new ConcurrentHashMap<>();

    // 获取锁的方法
    private ReentrantLock getLockForAddress(int address) {
        return addressLocks.computeIfAbsent(address, key -> new ReentrantLock(true)); // 公平锁
    }

    public Integer readHoldingRegisters(int address){
        try {
            CompletableFuture<ReadHoldingRegistersResponse> future = master
                    .sendRequest(new ReadHoldingRegistersRequest(address, 1), 0);
            ReadHoldingRegistersResponse readHoldingRegistersResponse = future.get();

            if (readHoldingRegistersResponse != null) {
                ByteBuf buf = readHoldingRegistersResponse.getRegisters();
                int i = buf.readUnsignedShort();
                ReferenceCountUtil.release(readHoldingRegistersResponse);
                return i;
            }
            return null;
        } catch (Exception e) {
            log.error("读取3号拆解区{}地址出错，错误原因：{}",address,e.toString());
            return null;
        }
    }

    public String readHoldingRegistersString(int address) {
        try {
            CompletableFuture<ReadHoldingRegistersResponse> future = master
                    .sendRequest(new ReadHoldingRegistersRequest(address, 1), 0);
            ReadHoldingRegistersResponse readHoldingRegistersResponse = future.get();
            if (readHoldingRegistersResponse != null) {
                ByteBuf buf = readHoldingRegistersResponse.getRegisters();
                byte[] bytes = new byte[2];
                buf.readBytes(bytes);
                ReferenceCountUtil.release(readHoldingRegistersResponse);
                return new String(bytes, StandardCharsets.UTF_8);
            }
            return null;
        } catch (InterruptedException | ExecutionException e) {
            log.error("读取3号拆解区{}地址出错，错误原因：{}",address,e.toString());
            return null;
        }

    }

    private boolean writeToRegister(int address, int value) throws Exception {
        try {
            // 发送异步写入请求
            CompletableFuture<ModbusResponse> future = master.sendRequest(new WriteSingleRegisterRequest(address, value), 0);

            // 阻塞等待结果返回，设置超时时间
            ModbusResponse response = future.get(100, TimeUnit.MILLISECONDS);

            // 检查响应是否为写入成功的类型
            if (response instanceof WriteSingleRegisterResponse) {
                WriteSingleRegisterResponse writeResponse = (WriteSingleRegisterResponse) response;

                // 校验返回的地址和值是否匹配
                if (writeResponse.getAddress() == address && writeResponse.getValue() == value) {
                    return true; // 写入成功
                } else {
                    log.warn("写入响应内容不匹配：地址={}, 值={}, 响应={}", address, value, writeResponse);
                }
            } else {
                log.warn("响应类型错误，预期=WriteSingleRegisterResponse，实际={}", response.getClass().getSimpleName());
            }

            return false; // 写入失败
        } catch (TimeoutException e) {
            log.error("写入超时，地址={}，值={}", address, value, e);
            return false; // 写入超时
        } catch (Exception e) {
            log.error("写入异常，地址={}，值={}", address, value, e);
            throw e; // 抛出异常以便外层重试逻辑处理
        }
    }

    public void writeWithRetry(int address, int value, int maxRetries) {
        Object lock = lockMap.computeIfAbsent(address, k -> new Object());
        int attempts = 0;
        synchronized (lock) {
            while (attempts < maxRetries) {
                try {
                    // 发送写入请求
                    CompletableFuture<ModbusResponse> future = master.sendRequest(new WriteSingleRegisterRequest(address, value), 0);
                    ModbusResponse response = future.get();

                    future.get();

                    // 如果没有异常，表示写入成功
                    log.info("写入成功，地址：{}，值：{}，尝试次数：{}", address, value, attempts + 1);
                    return;
                } catch (Exception e) {
                    // 捕获并记录每次失败的详细信息
                    log.error("写入失败，地址：{}，值：{}，尝试次数：{}，错误信息：{}", address, value, attempts + 1, e.getMessage(), e);
                }

                // 增加重试次数
                attempts++;

                // 可选：在每次重试间隔一定时间，以避免频繁调用（例如100毫秒）
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();  // 恢复中断状态
                    log.warn("重试机制中断，地址：{}，值：{}", address, value);
                    return;
                }
            }
        }

        // 如果达到最大重试次数仍然失败，则抛出异常
        throw new RuntimeException("多次尝试写入失败，地址：" + address + "，值：" + value);
    }
}
